﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Young3.FMSearch.Core.Offsets;
using Young3.FMSearch.Interface;
using Young3.FMSearch.Core.Managers;
using Young3.FMSearch.Core.Entities;
using System.Diagnostics;
using System.Threading;

/// © J. Jongboom, 2009
/// OffsetFinder finds the offset of a FM 2010 game


namespace FMSE.OffsetFinder
{
    class Program
    {

        static void Main(string[] args)
        {
            //[MemoryAddressAttribute(CountLength = 4, BytesToSkip = 0x18)]
            //Continent = 0x215e060,

            //first we have to find the continent array
            //in our base game Continent is at
            // 0x215e060

            Stopwatch sw = new Stopwatch(); sw.Start();

            FMProcess fmProcess = new FMProcess();
            Process[] fmProcesses = Process.GetProcessesByName("fm");
            Process[] wsmProcesses = Process.GetProcessesByName("wsm");

            if (fmProcesses.Length > 0 || wsmProcesses.Length > 0)
            {
                Process activeProcess;
                if (fmProcesses.Length > 0)
                    activeProcess = fmProcesses[0];
                else
                    activeProcess = wsmProcesses[0];

                fmProcess.Pointer = ProcessMemoryApi.OpenProcess(0x38, 1, (uint)activeProcess.Id);
                fmProcess.EndPoint = ProcessManager.GetProcessEndPoint(fmProcess.Pointer);
                fmProcess.Process = activeProcess;

                ProcessManager.fmProcess = fmProcess;

                ProcessManager.FmVersion = fmProcess.Process.MainModule.FileVersionInfo.ProductVersion;


                Console.WriteLine("FM Version " + ProcessManager.FmVersion);
            }
            else
            {
                Console.WriteLine("Didn't find the game");
                return;
            }

            Console.WriteLine(string.Format("Found game in memory ({0} ms)", sw.ElapsedMilliseconds));
            sw.Reset(); sw.Start();

            int numberOfObjects = -1;
            int continentCounter = 0;
            while (numberOfObjects != 7)
            {
                //we use baseObjectPointer in this case
                int baseObjectPointer = 0x215e060;
                baseObjectPointer += continentCounter;

                int memoryAddress;

                memoryAddress = ProcessManager.ReadInt32(baseObjectPointer);
                memoryAddress = ProcessManager.ReadInt32(memoryAddress + 0x18);
                memoryAddress += 4;

                numberOfObjects = ProcessManager.ReadInt32(memoryAddress + 4);

                if (numberOfObjects == 7)
                {
                    //we have a pointer here, so we do one last lookup
                    memoryAddress = ProcessManager.ReadInt32(memoryAddress);

                    List<Int32> MemoryAddresses;
                    byte[] pointerCache = ProcessManager.ReadProcessMemory(memoryAddress, Convert.ToUInt32(numberOfObjects * 4) + 4);
                    MemoryAddresses = new List<int>();
                    for (int i = 0; i < pointerCache.Length; i += 4)
                    {
                        int toAdd = ProcessManager.ReadInt32(pointerCache, i);
                        if (toAdd != 0) MemoryAddresses.Add(toAdd);
                    }

                    if (MemoryAddresses.Count != 7)
                    {
                        numberOfObjects = 0;
                    }
                }
                continentCounter++;

                if (continentCounter > 3000000)
                {
                    Console.WriteLine("Not found");
                    return;
                }
            }
            continentCounter--;
            Console.WriteLine("Game offset found: 0x" + continentCounter.ToString("X") + " (" + sw.ElapsedMilliseconds + " ms)");
            sw.Reset(); sw.Start();

            //whoehoe. now we have to find the playeroffsets
            Console.WriteLine("Staff offsets");
            var personadd =  GetPersonAddresses(continentCounter);
            foreach (var item in personadd)
            {
                Console.WriteLine("0x" + item.Key.ToString("X") + ": " + item.Value);
            }
            int lowest = personadd.OrderBy(s=>s.Value).First().Key;
            int personDiff = -(lowest - 0x1bfab94 - continentCounter - 0x168 - 8);

            Console.WriteLine(string.Format("Found staff offset 0x{1:X} ({0} ms)", sw.ElapsedMilliseconds, personDiff));
            
            Console.ReadKey(true);
        }

        private static Dictionary<int, int> GetPersonAddresses(int versionOffset)
        {
            Dictionary<int, int> bla = new Dictionary<int, int>();

            List<int> addresses = GetMemoryAddressesPlayer(versionOffset);

            //int maxBufferSize = (1 * 1024 * 1024) / 4; //256 kb
            List<List<int>> memoryAddressBatches = ObjectManager.SplitMemoryAddressesIntoBuffer(addresses, 16*1024*1024);

            int typeVersionCorrection = -(ProcessManager.VersionOffset - ProcessManager.StaffVersionOffset);
            foreach (var memoryBatch in memoryAddressBatches)
            {
                byte[] buffer = ProcessManager.ReadProcessMemory(memoryBatch.Min(), Convert.ToUInt32(memoryBatch.Max() + 2000 - memoryBatch.Min()));
                int lowestPointerInBatch = memoryBatch.Min();

                foreach (int memoryAddress in memoryBatch)
                {
                    //first 4 bytes contain the type
                    int type = ProcessManager.ReadInt32(buffer, memoryAddress + 0x0 - lowestPointerInBatch);

                    if (!bla.ContainsKey(type))
                        bla.Add(type, 0);

                    bla[type] += 1;
                }
            }

            return bla;
        }


        private static List<Int32> GetMemoryAddressesPlayer(int versionOffset)
        {
            List<Int32> MemoryAddresses = new List<int>();

            //        [MemoryAddressAttribute(CountLength = 4, BytesToSkip = 0x18)]
        //Person = 0x215e080,

            //we use baseObjectPointer in this case
            int baseObjectPointer = 0x215e080;
            baseObjectPointer += versionOffset;

            int memoryAddress;

            memoryAddress = ProcessManager.ReadInt32(baseObjectPointer);
                memoryAddress = ProcessManager.ReadInt32(memoryAddress + 0x18);
            memoryAddress += 4;

            int numberOfObjects = ProcessManager.ReadInt32(memoryAddress + 4);

            //we have a pointer here, so we do one last lookup
            memoryAddress = ProcessManager.ReadInt32(memoryAddress);

            MemoryAddresses = ObjectManager.GetMemoryAddresses(memoryAddress, numberOfObjects);

            return MemoryAddresses;
        }
    }

    internal struct StaffMemoryAddressesWrapper
    {
        public List<int> PlayerAddresses { get; set; }
        public List<int> StaffAddresses { get; set; }
        public List<int> PlayerStaffAddresses { get; set; }
        public List<int> HumanManagersAddresses { get; set; }
    }
}
